/* Turska Framework/UI library (Turs2)
 * Copyright 2003-2008 Jetro Lauha
 * All rights reserved.
 * Web: http://iki.fi/jetro/turska/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of EITHER:
 *   (1) The GNU Lesser General Public License as published by the Free
 *       Software Foundation; either version 2.1 of the License, or (at
 *       your option) any later version. The text of the GNU Lesser
 *       General Public License is included with this library in the
 *       file LICENSE-LGPL.txt.
 *   (2) The BSD-style license that is included with this library in
 *       the file LICENSE-BSD.txt.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
 * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
 *
 * $Id: App.cpp 73 2008-07-07 19:06:57Z jlauha $
 * $Revision: 73 $
 */

#include <time.h>
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#endif // __APPLE__
#include "main.h"

#define WINDOW_CAPTION_STR "Turs2 Framework Irr"

#define BLURMIX(a,b,n) ((a) * ((n) - 1) + (b)) / (n)


App * App::mSingleton = NULL;


App::App(irr::IrrlichtDevice *irrDevice) :
    mAlive(true),
    mTime(0),
    mStartTime(0),
    mNextUpdateTime(0),
    mCurrentAppStateHandler(NULL),
    mMouseX(0), mMouseY(0),
    mIrrDevice(irrDevice),
    mIrrVideoDriver(NULL),
    mIrrSceneManager(NULL),
    mGraphics(NULL)
{
}


App::~App()
{
}    


bool App::OnEvent(const irr::SEvent &event)
{
    if (mCurrentAppStateHandler == NULL)
        return false;

    switch (event.EventType)
    {
    case EET_GUI_EVENT:
        // TODO - forward irr gui events if necessary
        break;

    case EET_MOUSE_INPUT_EVENT:
        {
            /* for debug build, do not report events as handled so that
             * irr's built-in cameras get the events for debugging purposes.
             */
            /*
            #ifdef _DEBUG
            bool mouseEventHandledReturnValue = false;
            #else
            bool mouseEventHandledReturnValue = true;
            #endif
            */

            switch (event.MouseInput.Event)
            {
            case EMIE_LMOUSE_PRESSED_DOWN:
            case EMIE_MMOUSE_PRESSED_DOWN:
            case EMIE_RMOUSE_PRESSED_DOWN:
                return mCurrentAppStateHandler->onMouseDown(event.MouseInput);
                //return mouseEventHandledReturnValue;
            case EMIE_LMOUSE_LEFT_UP:
            case EMIE_MMOUSE_LEFT_UP:
            case EMIE_RMOUSE_LEFT_UP:
                return mCurrentAppStateHandler->onMouseUp(event.MouseInput);
                //return mouseEventHandledReturnValue;
            case EMIE_MOUSE_MOVED:
                mMouseX = event.MouseInput.X;
                mMouseY = event.MouseInput.Y;
                return mCurrentAppStateHandler->onMouseMotion(event.MouseInput);
                //return mouseEventHandledReturnValue;
            case EMIE_MOUSE_WHEEL:
                return mCurrentAppStateHandler->onMouseWheel(event.MouseInput);
                //return mouseEventHandledReturnValue;
            }
        }
        break;

    case EET_KEY_INPUT_EVENT:
        if (event.KeyInput.PressedDown)
            return mCurrentAppStateHandler->onKeyDown(event.KeyInput);
        else
            return mCurrentAppStateHandler->onKeyUp(event.KeyInput);
        //return true;

    case EET_LOG_TEXT_EVENT:
        // TODO - save log
        break;

    case EET_USER_EVENT:
        // Handle user event when necessary
        break;
    }


    /* 
     // TODO - app is closing down event - needs modifications to irrlicht (win32 WM_CLOSE)
     // ... setAlive(false);
     // TODO - focus lost/gain handling - needs modifications to irrlicht (win32 WM_ACTIVATE)
            case SDL_ACTIVEEVENT:
                if (mCurrentAppStateHandler != NULL &&
                    (event.active.state == SDL_APPINPUTFOCUS
                    // || event.active.state == SDL_APPACTIVE
                    ))
                {
                    //event.active.state = SDL_APPMOUSEFOCUS, SDL_APPINPUTFOCUS, SDL_APPACTIVE (iconify)
                    if (event.active.gain == 0)
                        mCurrentAppStateHandler->onFocusLost(event.active);
                    else if (event.active.gain == 1)
                        mCurrentAppStateHandler->onFocusGain(event.active);
                }
                break;
    */

    return false;
}


void App::setInstance(App *singleton)
{
    assert(mSingleton == NULL);
    mSingleton = singleton;
}


bool App::init(irr::IrrlichtDevice *irrDevice)
{
    setInstance(this);

    mIrrDevice = irrDevice;
    assert(mIrrDevice != NULL);
    if (mIrrDevice == NULL)
        return false;

    mIrrVideoDriver = mIrrDevice->getVideoDriver();
    assert(mIrrVideoDriver != NULL);
    if (mIrrVideoDriver == NULL)
        return false;
    mIrrSceneManager = mIrrDevice->getSceneManager();
    assert(mIrrSceneManager != NULL);
    if (mIrrSceneManager == NULL)
        return false;

    mGraphics = new GraphicsIrr(mIrrDevice);

    resetTime();
    srand((unsigned int)time(NULL));

    return true;
}


void App::run()
{
#ifdef __APPLE__
    // enable vsync
    long param = 1;
    CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &param);
#endif // __APPLE__

    // Warp mouse cursor position to middle of window/screen
    mMouseX = mPreviousMouseX = getScreenWidth() / 2;
    mMouseY = mPreviousMouseY = getScreenHeight() / 2;
    mIrrDevice->getCursorControl()->setPosition(mMouseX, mMouseY);

    core::stringw caption;
    caption += getWindowCaption();
    mIrrDevice->setWindowCaption(caption.c_str());

    #ifdef _DEBUG
    int fpsCounterFrames = 0;
    #endif
    while (mIrrDevice->run() && isAlive())
    {
        irr::ITimer *irrTimer = mIrrDevice->getTimer();
        const UI32 frameUpdateStart = irrTimer->getRealTime();

        bool updated = false;
        UI32 time = frameUpdateStart - mStartTime;
        mTime = BLURMIX(mTime, time, 4);

        updated |= update();

        if (updated)
        {
            if (mCurrentAppStateHandler != NULL)
                mCurrentAppStateHandler->render(mIrrDevice);
            render();

            mPreviousMouseX = mMouseX;
            mPreviousMouseY = mMouseY;
        }

        UI32 frameUpdateEnd = irrTimer->getRealTime();
        if (frameUpdateStart == frameUpdateEnd)
            mIrrDevice->yield();

        #ifdef _DEBUG
        if (++fpsCounterFrames == 100)
        {
            core::stringw str;
            str += getWindowCaption();
            str += L" [Driver: ";
            str += mIrrDevice->getVideoDriver()->getName();
            str += L"] FPS: ";
            str += (s32)mIrrDevice->getVideoDriver()->getFPS();
            mIrrDevice->setWindowCaption(str.c_str());
            fpsCounterFrames = 0;
        }
        #endif // _DEBUG
    }
}


bool App::update()
{
    if (mTime < mNextUpdateTime)
        return false;

    if (mCurrentAppStateHandler == NULL)
    {
        mNextUpdateTime = mTime;
        return false;
    }

    // TODO: Update this so that false is returned if the current
    //       state handler returned false for each iteration?
    while (mNextUpdateTime < mTime)
    {
        mNextUpdateTime += 1000.0 / mCurrentAppStateHandler->getUpdateFPS();
        
        mCurrentAppStateHandler->update((UI32)mNextUpdateTime);
    }

    return true;
}


void App::resetTime()
{
    mNextUpdateTime = 0;
    assert(mIrrDevice && mIrrDevice->getTimer());
    mStartTime = mIrrDevice->getTimer()->getRealTime();
    mTime = 0;
}


void App::setAppStateHandler(AppState *state)
{
    if (mCurrentAppStateHandler != NULL)
        mCurrentAppStateHandler->onDeactivate();

    mCurrentAppStateHandler = state;
    if (mCurrentAppStateHandler != NULL)
        mCurrentAppStateHandler->onActivate(0);

    resetTime();
}


C8 * App::getWindowCaption()
{
    return WINDOW_CAPTION_STR;
}
